package internal

import (
	"database/sql"
	"fmt"

	"gitee.com/hailib/mytree/errs"
	"github.com/jmoiron/sqlx"
	"go.uber.org/zap"
)

// Database 封装过的数据库，以便于执行某些统一的处理
type Database struct {
	DB     *sqlx.DB // 数据库连接，当数据库未连接或是连接断开时此值为 nil
	Logger Logger   // 日志工具
}

// NewDatabase 创建新的数据库
func NewDatabase(db *sqlx.DB, logger Logger) *Database {
	database := Database{
		DB:     db,
		Logger: logger,
	}

	return &database
}

// Begin 开始事务
func (d *Database) Begin(name string) (Tx, Error) {
	tx, err := d.DB.Beginx()
	if err != nil {
		return nil, errs.New(errs.TransactionError, 210901170702, "begin trans error")
	}
	d.Logger.Info("tx begin:", zap.String("tx", name))
	return NewTx(tx, d.Logger, name), nil
}

// Begin 开始事务
func (d *Database) Beginf(format string, args ...interface{}) (Tx, Error) {
	name := fmt.Sprintf(format, args...)
	return d.Begin(name)
}

// Exec 封装执行函数，以减少重复的代码
func (d *Database) Exec(cmd string, args ...interface{}) (sql.Result, Error) {
	d.Logger.Info("sqlx exec:", zap.String("sql", cmd), zap.Any("args", args))
	result, terr := d.DB.Exec(cmd, args...)
	if terr != nil {
		return result, errs.New(errs.DbUpdateError, 210901162422, "exec error, error: %s, sql: %s", terr.Error(), formatSql(cmd, args))
	}
	return result, nil
}

// Get 封装 sqlx get 方法以便于统一处理
//
func (d *Database) Get(dest interface{}, cmd string, args ...interface{}) Error {
	d.Logger.Debug("sqlx get :", zap.String("sql", cmd), zap.Any("args", args))
	err := d.DB.Get(dest, cmd, args...)
	if err != nil {
		return errs.New(errs.DbQueryError, 210901162322, "get error, error: %s, sql: %s", err.Error(), formatSql(cmd, args))
	}
	return nil
}

// In 格式化 In 语句
func (d *Database) In(query string, args ...interface{}) (sql string, params []interface{}, err Error) {
	sql, params, terr := sqlx.In(query, args...)
	if terr != nil {
		return "", nil, errs.New(errs.InvalidParam, 210907105818, "format in error, query: %s, args: %v", query, args)
	}
	return sql, params, nil
}

// Select 查询
func (d *Database) Select(dest interface{}, query string, args ...interface{}) Error {
	d.Logger.Debug("sqlx select:", zap.String("sql", query), zap.Any("args", args))
	err := d.DB.Select(dest, query, args...)
	if err != nil {
		return errs.New(errs.DbQueryError, 210902132949, "select error, sql: %s, error: %s", formatSql(query, args), err.Error())
	}
	return nil
}

// RowsAffected 确认受影响的行数
func (d *Database) RowsAffected(result sql.Result, min int, max int) Error {
	count, err := result.RowsAffected()

	if err != nil {
		return errs.New(errs.DbUpdateError, 210901163501, "get rows affected error, error: %s", err.Error())
	}

	if count < int64(min) || count > int64(max) {
		return errs.New(errs.DbUpdateError, 210901163354, "min: %d, max: %d, receive: %d", min, max, count)
	}

	return nil
}
